--------------------------------------------------------------------------
-- $File: //scripts/gamerules/PowerStruggle.lua $
-- $Revision$
-- $Date$
-- $Author$
--
-- $Id$
--------------------------------------------------------------------------
-- This file is maintained as part of the SSM AEGIS project. All additions
-- are the sole work of their authors to which all copyright and ownership
-- resides. However, each author grants license for the use of his or her
-- work to be a part of the SSM AEGIS project.
--
--  (C), Daniel 'Rod-Serling' Phelps, 2008
--  (C), Terry 'Charlie' Reinert, 2008
--------------------------------------------------------------------------
--  This work is based off of:
--  (C), Ralf 'Hawkeye' Ullrich, 2008
--  PowerStruggle.lua,v 1.2.2.2 2008/09/22 16:23:14
--
--  Crytek Source File.
--  Copyright (C), Crytek Studios, 2001-2006.
--------------------------------------------------------------------------

-- =============================================================================
-- Load dependent scripts.
-- =============================================================================
Script.LoadScript("scripts/gamerules/teaminstantaction.lua", 1, 1);
Script.LoadScript("scripts/gamerules/powerstruggleutils.lua", 1, 1);

-- =============================================================================
-- Define the PowerStruggle object. This object is derived from the
-- TeamInstantAction class. Once defined, various parameters can be
-- configured such as timers, player settings, team settings, and
-- other global parameters for the game mode.
--
-- **IMPORTANT**
-- Because of the fact that the power struggle game mode is derived
-- from the team instant action game mode, many functions will first
-- call the same function in the team instant action class and then
-- do any power struggle specific operations after. It is also important
-- to note that the team instant action game mode is derived from the
-- instant action game mode. So in many cases you have a power struggle
-- function call a team instant action function which then calls an
-- instant action function. This is typical OOP style programming. Make
-- sure that you DO NOT change the order in which things are done inside
-- the functions that do this... Remember, whichever is done second may
-- override things that were done in the first.
-- =============================================================================
PowerStruggle = new(TeamInstantAction);
PowerStruggle.States = { "Reset", "PreGame", "InGame", "PostGame", };

--
-- Define various timers that relate to specific tasks in
-- this game mode.
--
PowerStruggle.TEAM_CHANGE_MIN_TIME      = 60; -- time before allowing teamchange
PowerStruggle.NEXTLEVEL_TIME			= 22000;
PowerStruggle.ENDGAME_TIME				= 8000;
PowerStruggle.VEHICLE_CLAIM_TIME        = 90; -- seconds to claim a bought vehicle
PowerStruggle.NUKE_SPECTATE_TIMERID     = 1080;
PowerStruggle.NUKE_SPECTATE_TIME        = 2000;

--
-- Define spawn group parameters for this game mode. Note that
-- in power struggle all spawning is done by spawn groups that
-- are assigned to a specific team. This is unlike TIA in which
-- there are no team spawn points as all spawn points are open
-- to both teams. So the TIA spawn locations need to be set to
-- false and the team spawn locations and spawn groups need to
-- be set to true.
--
PowerStruggle.TIA_SPAWN_LOCATIONS       = false;
PowerStruggle.TEAM_SPAWN_LOCATIONS      = true;
PowerStruggle.USE_SPAWN_GROUPS          = true;
PowerStruggle.NEUTRAL_SPAWN_LOCATIONS   = false;

--
-- Define synched storage keys. These keys are much like network
-- data identifiers that specify the type of data being sent
-- in the message. These keys are used to sync player data with
-- the server and all the other clients attached to the game.
--
-- DO NOT CHANGE THESE VALUES!!!
--
PowerStruggle.PP_AMOUNT_KEY             = 200;
PowerStruggle.CP_AMOUNT_KEY             = 201;
PowerStruggle.RANK_KEY                  = 202;
PowerStruggle.TEAMPOWER_TEAM0_KEY       = 300;

--
-- Define the team name and the objects associated with each
-- team. Note that the team name "black" is used for the US
-- team and the name "tan" is used for the NK team.
--
PowerStruggle.teamName  ={ "tan", "black" };
PowerStruggle.teamModel =
{
    black   =
    {
        {
            "objects/characters/human/us/nanosuit/nanosuit_us_multiplayer.cdf",
            "objects/weapons/arms_global/arms_nanosuit_us.chr",
            "objects/characters/human/asian/nk_soldier/nk_soldier_frozen_scatter.cgf",
            "objects/characters/human/us/nanosuit/nanosuit_us_fp3p.cdf",
        },
    },

    tan     =
    {
        {
            "objects/characters/human/asian/nanosuit/nanosuit_asian_multiplayer.cdf",
            "objects/weapons/arms_global/arms_nanosuit_asian.chr",
            "objects/characters/human/asian/nk_soldier/nk_soldier_frozen_scatter.cgf",
            "objects/characters/human/asian/nanosuit/nanosuit_asian_fp3p.cdf",
        },
    },
}

PowerStruggle.VehiclePaint=
{
    black   =   "us",
    tan     = "nk",
}

-- =============================================================================
-- Define the radio messages sent using the function key commands.
--
-- Maximum 4 groups with 5 messages in each group (F5-F8, then 1-5).
-- Change Radio.cpp if more needed.
--
-- Definition format: {audio,text,nv}
-- audio = the audio message played to the client
-- text = the text sent to the client in the message
-- nv = the number of varients this message has (handled within C++ code)
--
-- Note: Changing these definitions does not update the text that
-- the player will see in the message GUI. That text is defined in
-- the localization XML files.
-- =============================================================================
PowerStruggle.teamRadio=
{
    black =
    {
        [1]=
        {
            {"mp_american/us_F5_1_10-4","@mp_radio_Yes",3},
            {"mp_american/us_F5_2_negative","@mp_radio_No",3},
            {"mp_american/us_F5_3_wait","@mp_radio_Wait",3},
            {"mp_american/us_F5_4_follow_me","@mp_radio_FollowMe",3},
            {"mp_american/us_F5_6_thank_you","@mp_radio_Thanks",3},
        },
        [2]=
        {
            {"mp_american/us_F6_1_attack_enemy_base","@mp_radio_TakeBase"},
            {"mp_american/us_F6_2_gather_power_cores","@mp_radio_GatherPower"},
            {"mp_american/us_F6_3_take_prototype_factory","@mp_radio_TakePT"},
            {"mp_american/us_F6_4_take_war_factory","@mp_radio_TakeWar"},
            {"mp_american/us_F6_5_take_airfield","@mp_radio_TakeAir"},
        },
        [3]=
        {
            {"mp_american/us_F7_1_armor_spotted","@mp_radio_ArmorSpotted"},
            {"mp_american/us_F7_2_aircraft_spotted","@mp_radio_AircraftSpotted"},
            {"mp_american/us_F7_3_boat_spotted","@mp_radio_BoatSpotted"},
            {"mp_american/us_F7_4_vehicle_spotted","@mp_radio_LTVSpotted"},
            {"mp_american/us_F7_5_infantry_spotted","@mp_radio_InfantrySpotted"},
        },
        [4]=
        {
            {"mp_american/us_F8_1_request_assistance","@mp_radio_Assistance"},
            {"mp_american/us_F8_2_get_into_vehicle","@mp_radio_GetIn"},
            {"mp_american/us_F8_3_get_out_vehicle","@mp_radio_GetOut"},
            {"mp_american/us_F8_4_mechanical_assistance_needed","@mp_radio_MechAssistance"},
            {"mp_american/us_F8_5_radar_scan","@mp_radio_Radar"},
        },
    },
    tan =
    {
        [1]=
        {
            {"mp_korean/nk_F5_1_10-4","@mp_radio_Yes",3},
            {"mp_korean/nk_F5_2_negative","@mp_radio_No",3},
            {"mp_korean/nk_F5_3_wait","@mp_radio_Wait",3},
            {"mp_korean/nk_F5_4_follow_me","@mp_radio_FollowMe",3},
            {"mp_korean/nk_F5_6_thank_you","@mp_radio_Thanks",3},
        },
        [2]=
        {
            {"mp_korean/nk_F6_1_attack_enemy_base","@mp_radio_TakeBase"},
            {"mp_korean/nk_F6_2_gather_power_cores","@mp_radio_GatherPower"},
            {"mp_korean/nk_F6_3_take_prototype_factory","@mp_radio_TakePT"},
            {"mp_korean/nk_F6_4_take_war_factory","@mp_radio_TakeWar"},
            {"mp_korean/nk_F6_5_take_airfield","@mp_radio_TakeAir"},
        },
        [3]=
        {
            {"mp_korean/nk_F7_1_armor_spotted","@mp_radio_ArmorSpotted"},
            {"mp_korean/nk_F7_2_aircraft_spotted","@mp_radio_AircraftSpotted"},
            {"mp_korean/nk_F7_3_boat_spotted","@mp_radio_BoatSpotted"},
            {"mp_korean/nk_F7_4_vehicle_spotted","@mp_radio_LTVSpotted"},
            {"mp_korean/nk_F7_5_infantry_spotted","@mp_radio_InfantrySpotted"},
        },
        [4]=
        {
            {"mp_korean/nk_F8_1_request_assistance","@mp_radio_Assistance"},
            {"mp_korean/nk_F8_2_get_into_vehicle","@mp_radio_GetIn"},
            {"mp_korean/nk_F8_3_get_out_vehicle","@mp_radio_GetOut"},
            {"mp_korean/nk_F8_4_mechanical_assistance_needed","@mp_radio_MechAssistance"},
            {"mp_korean/nk_F8_5_radar_scan","@mp_radio_Radar"},
        },
    }
}

-- =============================================================================
-- Net Expose
--
-- TODO: I believe these functions having something to do with the data
--       that is synched across the network but I am not sure exactly how
--       this happens. I will put what I do know here and will update it
--       in the future whenever I get it fully figured out.
--
--       The methods listed in the ClientMethods array are all defined in
--       other scripts using the .Client scope. For example, ClPP is defined
--       in PowerStruggleBuying.lua as PowerStruggle.Client:ClPP(amount, kill).
--
--       The first two parameters are common to all the methods listed below.
--       Param1 is either RELIABLE_ORDERED or RELIABLE_UNORDERED and Param2
--       is either NO_ATTACH or POST_ATTACH. The remaining parameters match
--       the types in the actual function definition in the other scripts.
--       Using the ClPP method above as an example, the parameters in the
--       definition are amount and kill. In the list below the ClPP method
--       has FLOAT and BOOL for these parameters which specify the type.
-- =============================================================================
Net.Expose {
    Class = PowerStruggle,

    ClientMethods = {
        ClSetupPlayer               = { RELIABLE_UNORDERED, NO_ATTACH, DEPENTITYID, },
        ClSetSpawnGroup             = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, },
        ClSetPlayerSpawnGroup       = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, ENTITYID },
        ClSpawnGroupInvalid         = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, },
        ClVictory                   = { RELIABLE_ORDERED, POST_ATTACH, INT8, INT8, ENTITYID },

        ClStartWorking              = { RELIABLE_ORDERED, POST_ATTACH, ENTITYID; STRINGTABLE },
        ClStepWorking               = { RELIABLE_ORDERED, POST_ATTACH, INT8 },
        ClStopWorking               = { RELIABLE_ORDERED, POST_ATTACH, ENTITYID, BOOL },
        ClWorkComplete              = { RELIABLE_ORDERED, POST_ATTACH, ENTITYID, STRINGTABLE },


        ClClientConnect             = { RELIABLE_UNORDERED, POST_ATTACH, STRING, BOOL },
        ClClientDisconnect          = { RELIABLE_UNORDERED, POST_ATTACH, STRING, },
        ClClientEnteredGame         = { RELIABLE_UNORDERED, POST_ATTACH, STRING, },

        ClEnterBuyZone              = { RELIABLE_ORDERED, NO_ATTACH, DEPENTITYID, BOOL };
        ClEnterServiceZone          = { RELIABLE_ORDERED, NO_ATTACH, DEPENTITYID, BOOL };
        ClEnterCaptureArea          = { RELIABLE_ORDERED, NO_ATTACH, DEPENTITYID, BOOL };
        ClResetBuyZones             = {RELIABLE_ORDERED, POST_ATTACH, };

        ClPerimeterBreached         = { RELIABLE_ORDERED, POST_ATTACH, ENTITYID };
        ClTurretHit                 = { RELIABLE_ORDERED, POST_ATTACH, ENTITYID };
        ClHQHit                     = { RELIABLE_ORDERED, POST_ATTACH, ENTITYID };
        ClTurretDestroyed           = { RELIABLE_ORDERED, POST_ATTACH, ENTITYID };

        ClMDAlert                   = { RELIABLE_UNORDERED, POST_ATTACH, STRING },
        ClMDAlert_ToPlayer          = { RELIABLE_UNORDERED, POST_ATTACH, },
        ClTimerAlert                = { RELIABLE_UNORDERED, POST_ATTACH, INT8 },

        ClBuyError                  = { RELIABLE_UNORDERED, POST_ATTACH, STRING, },
        ClBuyOk                     = { RELIABLE_UNORDERED, POST_ATTACH, STRING, },

        ClPP                        = { RELIABLE_UNORDERED, POST_ATTACH, FLOAT, BOOL },
        ClRank                      = { RELIABLE_UNORDERED, POST_ATTACH, INT8, BOOL },
        ClTeamPower                 = { RELIABLE_UNORDERED, POST_ATTACH, INT8, FLOAT },

        ClEndGameNear               = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID },

        ClReviveCycle               = { RELIABLE_UNORDERED, POST_ATTACH, BOOL },
    },

    ServerMethods = {
        RequestRevive               = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, },
        RequestSpawnGroup           = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, ENTITYID },

        SvBuy                       = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, STRINGTABLE },
        SvBuyAmmo                   = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, STRINGTABLE },
        SvRequestPP                 = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, INT32 };
        RequestSpectatorTarget      = { RELIABLE_UNORDERED, POST_ATTACH, ENTITYID, INT8 },
    },

    ServerProperties =
    {
    },
};


-- =============================================================================
-- Function: PowerStruggle.Server:OnInit
--
-- This function contains initialization code for the
-- power struggle game mode.
-- =============================================================================
function PowerStruggle.Server:OnInit()
    TeamInstantAction.Server.OnInit(self);
    XGetModFiles("PS");
end


-- =============================================================================
-- Function: PowerStruggle.Client:OnInit
--
-- This function contains initialization code for the
-- power struggle game mode.
-- =============================================================================
function PowerStruggle.Client:OnInit()
    TeamInstantAction.Client.OnInit(self);
    self:ResetCaptureProgress();
end


-- =============================================================================
-- Function: PowerStruggle.Client:OnReset
--
-- This function resets the game mode parameters.
-- =============================================================================
function PowerStruggle.Client:OnReset()
    TeamInstantAction.Client.OnReset(self);
    self:ResetCaptureProgress();
end

-- =============================================================================
-- Function: PowerStruggle:GatherEntities
--
-- This function creates tables containing the entities for
-- Factories, Objectives, Turrets, and HQ's.
-- =============================================================================
function PowerStruggle:GatherEntities()
    self.turrets={};
    self.hqs = {};

  -- Scan through all the factory entities and add them
  -- to the table.
    local entities=System.GetEntitiesByClass("Factory");
    if (entities) then
        self.factories={};
        for i,v in pairs(entities) do
            table.insert(self.factories, v);
        end
    end

  -- Scan through all the objective entities and add them
  -- to the table.
    local entities=System.GetEntitiesByClass("Objective");
    if (entities) then
        self.objectives={};
        for i,v in pairs(entities) do
            table.insert(self.objectives, v);
        end
    end

    -- Scan through all the auto turret entities and add them
  -- to the table.
    local turrets=System.GetEntitiesByClass( "AutoTurret" );
    if (turrets) then
        for i,turret in pairs(turrets) do
            table.insert(self.turrets, turret.id);
        end
    end

    -- Scan through all the AA auto turret entities and add them
  -- to the table.
    local aaturrets=System.GetEntitiesByClass( "AutoTurretAA" );
    if (aaturrets) then
        for i,turret in pairs(aaturrets) do
            table.insert(self.turrets, turret.id);
        end
    end

    -- Scan through all the HQ entities and add them
  -- to the table.
    local hqs=System.GetEntitiesByClass( "HQ" );
    for i,hq in pairs(hqs) do
        table.insert(self.hqs, hq.id);
    end
end

-- =============================================================================
-- Function: PowerStruggle.Server:OnStartGame
--
-- This function is called when the power struggle game
-- begins. It simply calls the parent game mode class
-- in order to initialize game parameters and then calls
-- the GatherEntities function to populate the entity tables.
-- =============================================================================
function PowerStruggle.Server:OnStartGame()
    TeamInstantAction.Server.OnStartGame(self);
    self:GatherEntities();
end

-- =============================================================================
-- Function: PowerStruggle.Client:OnStartGame
--
-- This function is called when the power struggle game
-- begins. It sets the objectives for the player and
-- calls the GatherEntities function to populate the entity
-- tables.
-- =============================================================================
function PowerStruggle.Client:OnStartGame()
    if (HUD) then
        HUD.SetObjectiveStatus("PS.Obj1_CapturePT", MO_DEACTIVATED, 1);
        HUD.SetObjectiveStatus("PS.Obj2_SecureAliens", MO_DEACTIVATED, 1);
        HUD.SetObjectiveStatus("PS.Obj3_BuildTAC", MO_DEACTIVATED, 1);
        HUD.SetObjectiveStatus("PS.Obj4_DestroyHQ", MO_DEACTIVATED, 1);
        HUD.SetObjectiveStatus("PS.SecObj1_Factory", MO_DEACTIVATED, 1);
        HUD.SetObjectiveStatus("PS.SecObj2_Bunker", MO_DEACTIVATED, 1);
        HUD.SetObjectiveStatus("PS.SecObj3_Turret", MO_DEACTIVATED, 1);
        HUD.SetMainObjective("PS.Obj1_CapturePT");
    end

    if (not self.isServer) then
        self:GatherEntities();
    end
end


-- =============================================================================
-- Function: PowerStruggle:Reset
--
-- This function resets the power struggle game mode. It
-- simply clears out all the parameters associated with game
-- mode play and calls a few reset functions for other game
-- objects.
--
-- \param[in] forcePregame
-- See function header comments for TeamInstantAction.Reset().
-- =============================================================================
function PowerStruggle:Reset(forcePregame)
    if (g_localActor and HUD) then
        HUD.ResetBuyZones();
    end
    self.inBuyZone={};
    self.inServiceZone={};
    self.unclaimedVehicle={};
    self.reviveQueue={};

    self:ResetMinimap();
    self:ResetPower();

    self.game:ResetReviveCycleTime();

-- ===============================================
-- RACE SECTION
-- ===============================================
RaceStarted = false;
LightsOn = false;
recordtime = 3.30;
recorddriver = "Michael Schumacher";
recordvehicle = "Ferrari F2001";
-- ===============================================

    TeamInstantAction.Reset(self, forcePregame);
end


-- =============================================================================
-- Function: PowerStruggle.Server:OnClientConnect
--
-- This function is called whenever a client connects to
-- the server. It basically resets the game parameters for
-- the joining player.
--
-- \param[in] channelId
-- The channel Id of the joining player.
--
-- \param[in] reset
-- \param[in] name
-- See function header comments for TeamInstantAction.Server.OnClientConnect().
-- =============================================================================
function PowerStruggle.Server:OnClientConnect(channelId, reset, name)
    local player=TeamInstantAction.Server.OnClientConnect(self, channelId, reset, name);

    --
    -- If the client channel is not on hold then reset the
    -- score, PP, and CP of the player joining from this
    -- channel.
    --
    if (not CryAction.IsChannelOnHold(channelId)) then
        self:ResetScore(player.id);
        self:ResetPP(player.id);
        self:ResetCP(player.id);
    end

    self:ResetRevive(player.id);
end


-- =============================================================================
-- Function: PowerStruggle.Server:OnClientEnteredGame
--
-- This function is called whenever a player enters the game.
-- The original code simply gave the player the starting PP
-- and reset the buy zone data for the player. Additionally,
-- the "Average Rank Adjust" feature is implemented here. If
-- it is turned on in the xastrada.lua file then the average
-- rank of all players on the server is calculated and the
-- joining player will either be granted that average rank or
-- the defined maximum grant rank, whichever is larger.
--
-- \param[in] channelId
-- The channel Id of the joining player.
--
-- \param[in] player
-- The object of the player.
--
-- \param[in] reset
-- See function header comments in TIA class.
-- =============================================================================
function PowerStruggle.Server:OnClientEnteredGame(channelId, player, reset)
    TeamInstantAction.Server.OnClientEnteredGame(self, channelId, player, reset);

    if (player) then

        --self:SetPlayerPP(player.id, self.ppList.START); --Initial PP is now set in AEGIS:PlayerInitialStats, when ProfileID is established.
        --
        -- Default the buy zone settings on the entering player and then check to
        -- see if the player is going to be spawning in a buy zone. If so then set
        -- up the buy zone for the player.
        --
        self.onClient:ClResetBuyZones(player.actor:GetChannel());

        if (self.inBuyZone[player.id]) then
            for zoneId,yes in pairs(self.inBuyZone[player.id]) do
                if (yes) then
                    self.onClient:ClEnterBuyZone(player.actor:GetChannel(), zoneId, true);
                end
            end
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle.Server:OnClientDisconnect
--
-- This function is called whenever a player disconnects.
-- It cleans up all game entities associated with this
-- player to include vehicles.
--
-- \param[in] channelId
-- The channel Id of the player that disconnected.
-- =============================================================================
function PowerStruggle.Server:OnClientDisconnect(channelId)
    TeamInstantAction.Server.OnClientDisconnect(self, channelId);
    local player=self.game:GetPlayerByChannelId(channelId);


	--Save scores for reconnections.
   	if (XCfgVar.usePersistantScores) then
        local profile = XGetIdentityValue(player, "profile");
		if (not profile) then
			return;
		end
		if (not AEGIS.PlayerInformationByProfile[profile].scores) then
		    AEGIS.PlayerInformationByProfile[profile].scores = {};
		end

		local tmp = {};
		tmp = {
			[self.PP_AMOUNT_KEY] = self.game:GetSynchedEntityValue(player.id, self.PP_AMOUNT_KEY) or 0;
			[self.CP_AMOUNT_KEY] = self.game:GetSynchedEntityValue(player.id, self.CP_AMOUNT_KEY) or 0;
			[self.RANK_KEY] = self.game:GetSynchedEntityValue(player.id, self.RANK_KEY) or 0;
		};
		--Merge this table with our actual storage location, allowing
		--InstationAction:OnClientDisconnect to manage kills/deaths/headshots.
		XMergeTable(AEGIS.PlayerInformationByProfile[profile].scores,tmp);
	end
	
    if (player) then
        self:ResetRevive(player.id, true);

        self:VehicleOwnerDeath(player);
        self:ResetUnclaimedVehicle(player.id, true);

        self.inBuyZone[player.id]=nil;
        self.inServiceZone[player.id]=nil;
    end
end

-- =============================================================================
-- Function: PowerStruggle:CheckTimeLimit
--
-- This function checks to see if the game is over and if so
-- it determines the winner or adds overtime to the game.
-- =============================================================================
function PowerStruggle:CheckTimeLimit()
    --
    -- If there is a time limit to the game and the remaining time is 0 or less
    -- then there is a possibility that the game is over and we need to check to
    -- see if we need to end the game or add more time.
    --
    if (self.game:IsTimeLimited() and self.game:GetRemainingGameTime()<=0) then
        --
        -- If the game state is not "InGame" then do nothing since only a game that
        -- has been in play can be ended. This protects against the rest of this
        -- function being run whenever the timer runs out for the PreGame or
        -- PostGame states since those events are handled by a different function.
        --
        local state=self:GetState();
        if (state and state~="InGame") then
            return;
        end

        local draw=false;   -- True if the game is a draw and no one wins.

        local maxE;
        local maxTeamId;

        for i,teamId in pairs(self.teamId) do
            local energy = self:GetTeamPower(teamId);
            if (not maxE) then
                --
                -- This is the first team in the list to be checked. So save off the
                -- energy level and team id for this team so it can be compared with
                -- the next teams data on the next pass through the for loop.
                --
                maxE=energy;
                maxTeamId=teamId;
            else
                if (maxE == energy) then
                    --
                    -- Both teams have the same energy level so the game is a draw.
                    --
                    -- TODO: This can be expanded to look at other factors in determining
                    --       which team actually wins. We could see which team has a higher
                    --       average rank or which team has more combined kills. This may
                    --       not be ideal though due to auto team balancing.
                    --
                    draw = true;
                else
                    if(energy > maxE) then
                        --
                        -- This means that the second team has more energy than the first
                        -- team so save off their energy level and team id since they are
                        -- the winner.
                        --
                        maxE=energy;
                        maxTeamId=teamId;
                    end
                end
            end
        end

        if (not draw) then
            --
            -- There was a winner so end the game.
            --
            self:OnGameEnd(maxTeamId, 2);
        else
            --
            -- Both teams are currently tied so determine whether to add overtime or
            -- end the game as a draw.
            --
            if(self.overtimeAdded and self.overtimeAdded >= XCfgVar.numOvertimePeriods) then
                --
                -- The max number of overtime periods has been reached so just end the
                -- game as a draw.
                --
                self:OnGameEnd(nil, 2);
            else
                --
                -- The max number of overtime periods has not been reached so add the
                -- overtime time amount to the remaining time, announce this to the
                -- players, and then increment the overtime period counter.
                --
                self.game:AddOvertime(XCfgVar.overtimeMinutesToAdd);
                self.game:SendTextMessage(TextMessageBig, "@ui_msg_overtime_0", TextMessageToAll, nil, XCfgVar.overtimeMinutesToAdd);

                if (self.overtimeAdded) then
                    self.overtimeAdded = self.overtimeAdded + 1;
                else
                    self.overtimeAdded = 1;
                end
            end
        end
    end
end

-- =============================================================================
-- Function: PowerStruggle:AutoTeamBalanceSwitchPlayer
--
-- This function switches a player to the other team and
-- sends messages to all players letting them know of the
-- switch.
--
-- \param[in] player
-- The object of the player to be switched.
--
-- \param[in] dstTeamId
-- The team Id of the team the player will be switched to.
-- =============================================================================
function PowerStruggle:AutoTeamBalanceSwitchPlayer(player, dstTeamId)
    self.game:SendTextMessage(TextMessageCenter, "@mp_AutoTeamBalanceSwapYou", TextMessageToClient, player.id);
    self.game:SendTextMessage(TextMessageCenter, "@mp_AutoTeamBalanceSwap", TextMessageToOtherClients, player.id, player:GetName());
    self:QueueRevive(player.id);
    self.Server.RequestSpawnGroup(self, player.id, NULL_ENTITY, true);
    self.game:SetTeam(dstTeamId, player.id);
    self.Server.RequestSpawnGroup(self, player.id, self.game:GetTeamDefaultSpawnGroup(dstTeamId) or NULL_ENTITY, true);
    player.last_team_change=_time;
end

-- =============================================================================
-- Function: PowerStruggle:AutoTeamBalanceCanSwitchPlayer
--
-- This function determines whether the player can be
-- switched to the other team.
--
-- \param[in] player
-- The entity of the player to be switched.
--
-- \param[unused] dstTeamId
-- The team Id of the team the player will be switched to.
--
-- \return
-- False if player is a protected admin
-- True if the player is dead.
-- =============================================================================
function PowerStruggle:AutoTeamBalanceCanSwitchPlayer(player, dstTeamId)

    if (XCfgVar.useAdminImmuneToTeamBalanceFeature) then
		if (XIsAdmin(player)) then
		    return false;
		end
    end
    if (XCfgVar.usePremiumImmuneToTeamBalanceFeature) then
		if (XIsPremium(player)) then
		    return false;
		end
    end
    return player:IsDead();
end

-- =============================================================================
-- Function: PowerStruggle:SetUnclaimedVehicle
--
-- This function updates the parameters associated with an
-- unclaimed vehicle and adds the vehicle to the unclaimed
-- vehicle array.
--
-- \param[in] vehicleId
-- The Id of the vehicle in question.
--
-- \param[in] ownerId
-- The Id of the vehicles owner.
--
-- \param[in] teamId
-- The Id of the team the vehicle belongs to (owners team).
--
-- \param[in] vehicleName
-- The name of the vehicle.
--
-- \param[in] buildingId
-- The Id of the building in which the vehicle was built.
--
-- \param[in] gateId
-- The Id of the gate in which the vehicle was built.
-- =============================================================================
function PowerStruggle:SetUnclaimedVehicle(vehicleId, ownerId, teamId, vehicleName, buildingId, gateId)
    local vehicle={};

    vehicle.ownerId=ownerId;
    vehicle.teamId=teamId;
    vehicle.name=vehicleName;
    vehicle.buildingId=buildingId;
    vehicle.gate=gateId;
    vehicle.time=self.VEHICLE_CLAIM_TIME;

    self.unclaimedVehicle[vehicleId]=vehicle;
end


-- =============================================================================
-- Function: PowerStruggle:UpdateUnclaimedVehicles
--
-- This function checks to see if an unclaimed vehicle
-- should be refunded to the owner. This is when a vehicle
-- is built but the owner never gets into it. After a set
-- amount of time the vehicle will be destroyed and some
-- PP based on the refund multiplier will be returned to
-- the player.
--
-- \param[in] frameTime
-- The current frame time of the game.
-- =============================================================================
function PowerStruggle:UpdateUnclaimedVehicles(frameTime)
    for id,v in pairs(self.unclaimedVehicle) do
        v.time=v.time-frameTime;
        if (v.time<=0) then
            --
            -- The vehicle has remained unclaimed for longer than the allowed time.
            -- Thus, notify the player that the vehicle is being cleaned up and then
            -- refund some amount of PP to the player that is based on the vehicle
            -- refund multiplier set in the configuration.
            --
            self.game:SendTextMessage(TextMessageInfo, "@mp_UnclaimedVehicle", TextMessageToClient, v.ownerId, g_gameRules:GetItemName(v.name));
            local price=self:GetPrice(v.name);
            if (price and price>0) then
                self:AwardPPCount(v.ownerId, math.floor(self.ppList.VEHICLE_REFUND_MULT*price+0.5));
            end

            --
            -- Remove the entity from the game and remove it from the unclaimed
            -- vehicle array.
            --
            System.RemoveEntity(id);
            self.unclaimedVehicle[id]=nil;
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle:ClaimedVehicle
--
-- This function sets the ownership of a vehicle to the player and
-- removes the vehicle from the unclaimed vehicle array.
--
-- \param[in] vehicleId
-- The Id of the vehicle.
--
-- \param[in] playerId
-- The Id of the player claiming the vehicle.
-- =============================================================================
function PowerStruggle:ClaimedVehicle(vehicleId, playerId)
    local vehicle=self.unclaimedVehicle[vehicleId];

    if (vehicle and vehicle.ownerId == playerId) then
        self.unclaimedVehicle[vehicleId]=nil;
    end
end


-- =============================================================================
-- Function: PowerStruggle:ResetUnclaimedVehicle
--
-- This function resets an unclaimed vehicle.
--
-- \param[in] playerId
-- The Id of the player.
--
-- \param[in] unlock
-- True if the vehicle should be unlocked so that any player
-- on the same team as the vehicle (i.e. the same team as the
-- vehicles owner) can enter the vehicle.
-- =============================================================================
function PowerStruggle:ResetUnclaimedVehicle(playerId, unlock)
    for i,v in pairs(self.unclaimedVehicle) do
        if (v.ownerId==playedId) then
            if (unlock) then
                --
                -- The vehicle should be unlocked so that anyone on the owners team can
                -- enter it. Thus the owner Id is set to the null entity and the team
                -- Id for the vehicle is set to the team Id of the owner.
                --
                vehicle.vehicle:SetOwnerId(NULL_ENTITY);
                self.game:SetTeam(v.teamId, v.id);
            end

            --
            -- Remove the vehicle from the unclaimed vehicle array.
            --
            self.unclaimedVehicle[i]=nil;
            return;
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle:OnPurchaseCancelled
--
-- This function refunds the price and energy of a vehicle
-- when the purchase is cancelled.
--
-- \param[in] playerId
-- The Id of the player that made the purchase.
--
-- \param[in] teamId
-- The Id of the players team (needed to refund energy).
--
-- \param[in] itemName
-- The name of the item that was purchased / cancelled.
-- =============================================================================
function PowerStruggle:OnPurchaseCancelled(playerId, teamId, itemName)
    local price,energy=self:GetPrice(itemName);

    --
    -- If the item has a PP cost associated with it then refund the PP to the
    -- player that made the purchase.
    --
    if (price>0) then
        self:AwardPPCount(playerId, price);
    end

    --
    -- If the item has an energy cost associated with it then refund that energy
    -- amount to the team since the purchase was cancelled.
    --
    if (energy and energy>0) then
        self:SetTeamPower(teamId, self:GetTeamPower(teamId)+energy);
    end
end


-- =============================================================================
-- Function: PowerStruggle:OnVehicleUnlocked
--
-- This function is the event handler for when a player
-- unlocks a vehicle. It sets the vehicles team to the
-- team of the unlocking player and removes the vehicle
-- from the unclaimed vehicle array.
--
-- \param[in] vehicleId
-- The Id of the vehicle being unlocked.
--
-- \param[in] playerId
-- The Id of the player unlocking the vehicle.
-- =============================================================================
function PowerStruggle:OnVehicleUnlocked(vehicleId, playerId)
    g_gameRules.game:SendTextMessage(TextMessageInfo, "@mp_VehicleUnlocked", TextMessageToClient, playerId);
    g_gameRules.game:SetTeam(self.game:GetTeam(playerId), vehicleId);

    local vehicle=System.GetEntity(vehicleId);
    if (vehicle) then
        vehicle.vehicle:SetOwnerId(NULL_ENTITY);
    end

    self.unclaimedVehicle[vehicleId]=nil;
end


-- =============================================================================
-- Function: PowerStruggle.Server:OnVehicleDestroyed
--
-- This function cleans up any buy zones associated with
-- a vehicle when it is destroyed.
--
-- \param[in] vehicleId
-- The Id of the vehicle that was destroyed.
-- =============================================================================
function PowerStruggle.Server:OnVehicleDestroyed(vehicleId)
    for playerId,zones in pairs(self.inBuyZone) do
        if (zones[vehicleId]) then
            local player=System.GetEntity(playerId);
            if (player and player.actor) then
                self.onClient:ClEnterBuyZone(player.actor:GetChannel(), vehicleId, false);
            end

            zones[vehicleId]=nil;
        end
    end

    for playerId,zones in pairs(self.inServiceZone) do
        if (zones[vehicleId]) then
            local player=System.GetEntity(playerId);
            if (player and player.actor) then
                self.onClient:ClEnterServiceZone(player.actor:GetChannel(), vehicleId, false);
            end

            zones[vehicleId]=nil;
        end
    end

    self.unclaimedVehicle[vehicleId]=nil;
end


-- =============================================================================
-- Function: PowerStruggle:OnVehicleSubmerged
--
-- This function is the event handler for when a vehicle is
-- submerged. Currently it simply destroys the vehicle.
--
-- \param[in] vehicleId
-- The Id of the submerged vehicle.
--
-- \param[unused] ratio
-- =============================================================================
function PowerStruggle:OnVehicleSubmerged(vehicleId, ratio)
    self:OnVehicleDestroyed(vehicleId);
end


-- =============================================================================
-- Function: PowerStruggle.Client.OnSetBuyFlags
--
-- This function updates the buy list in the HUD.
--
-- \param[unused] g_gameRules
-- \param[unused] entityId
-- \param[unused] flags
-- =============================================================================
function PowerStruggle.Client.OnSetBuyFlags(g_gameRules, entityId, flags)
    if (HUD) then
        HUD.UpdateBuyList();
    end
end


-- =============================================================================
-- Function: PowerStruggle:AbandonPlayerVehicle
--
-- This function destroys a vehicle if the player abondoned
-- it. This can happen when a player takes ownership of a
-- second vehicle and the first vehicle is not a spawn group.
-- If destroy is set to false then a timer is started instead
-- of immediately destroying the vehicle.
--
-- \param[in] playerId
-- The Id of the vehicle owner.
--
-- \param[in] currentVehicleId
-- The Id of the new vehicle the player just took ownership of.
--
-- \param[in] destroy
-- If set to true then the vehicle is destroyed immediately.
-- If set to false then a timer is started.
-- =============================================================================
function PowerStruggle:AbandonPlayerVehicle(playerId, currentVehicleId, destroy)
    local player=System.GetEntity(playerId);
    if (not player) then
        return;
    end

    --
    -- We need to check to see if a vehicle has been abandoned only if the player
    -- that has ownership of it becomes the owner of another vehicle. If that is
    -- the case then we have to run addition checks to see if a vehicle is truly
    -- abandoned.
    --
    if (player.lastPSVehicleId and ((not currentVehicleId) or player.lastPSVehicleId~=currentVehicleId)) then
        local lastVehicle=System.GetEntity(player.lastPSVehicleId);
        if (lastVehicle) then
            --
            -- A vehicle is considered to be abandoned when the player that is set as
            -- the owner becomes the owner of a different vehicle, the vehicle is empty,
            -- and the vehicle does not have a spawn group assigned to it. Note that
            -- vehicles with spawn groups are never abandoned.
            --
            if (lastVehicle.lastOwnerId and lastVehicle.lastOwnerId==playerId and lastVehicle.vehicle:IsEmpty() and (not self.game:IsSpawnGroup(player.lastPSVehicleId))) then
                --
                -- Two things can happen when a vehicle is abandoned and the destroy
                -- parameter is used to make the decision. Either the vehicle is
                -- immediately destroyed (destroy = true) or an abandon timer is
                -- started for the vehicle.
                --
                if (destroy) then
                    lastVehicle.vehicle:Destroy();
                else
                    --
                    -- Set the AbandonTimer to our configurable variable
                    --
                    lastVehicle.vehicle:StartAbandonTimer(true, XCfgVar.vehicleAbandonTime);
                end
            end
            lastVehicle.lastOwnerId=nil;
        end
        player.lastPSVehicleId=nil;
    end
end


-- =============================================================================
-- Function: PowerStruggle:OnEnterVehicleSeat
--
-- This function is called whenever a player enters a seat
-- in a vehicle. This function makes the function call to
-- abandon any other vehicle the player has ownership of and
-- also stops the abandon timer on the new vehicle if it exists.
--
-- It is important to note that the first thing this function
-- does is calls the same function defined in the TIA class.
-- A lot more things are going on here than you can see here.
-- For example, setting the team is now done in the TIA function
-- and the giving of a parachute is done in the IA function which
-- is called by the TIA function.
--
-- \param[in] vehicle
-- The entity of the vehicle the player just entered.
--
-- \param[in] seat
-- The entity of the seat the player just entered.
--
-- \param[in] entityId
-- The Id of the entity that just entered the vehicle. This
-- is usually the player but could be an AI entity.
-- =============================================================================
function PowerStruggle:OnEnterVehicleSeat(vehicle, seat, entityId)
    TeamInstantAction.OnEnterVehicleSeat(self, vehicle, seat, entityId);

    if (self.isServer) then
        --
        -- We need to check to see if another vehicle will become abandoned due to
        -- a player entering a vehicle. We do this for all vehicles regardless of
        -- type and the AbandonPlayerVehicle function handles the specifics for the
        -- special vehicle types (those with spawn groups for example).
        --
        local player=System.GetEntity(entityId);
        if (player) then
            self:AbandonPlayerVehicle(player.id, vehicle.id);
        end

        --
        -- If the player that is entering the vehicle is the owner then we set the
        -- owner id to the null entity. Whenever the owner Id is set then the vehicle
        -- is reserved for that player and no other players can enter the vehicle.
        -- By setting the owner Id to the null entity then the players team mates can
        -- enter the vehicle from this point on.
        --
        if(entityId==vehicle.vehicle:GetOwnerId()) then
            vehicle.vehicle:SetOwnerId(NULL_ENTITY);
        end

        --
        -- Since the vehicle now has a player or AI inside of it we kill the abandon
        -- timer and then remove the vehicle from the unclaimed vehicle array.
        --
        vehicle.vehicle:KillAbandonTimer();

        if (self.unclaimedVehicle[vehicle.id]) then
            self.unclaimedVehicle[vehicle.id]=nil;
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle:OnLeaveVehicleSeat
--
-- This function is called whenever a player leaves a vehicles seat regardless
-- as to whether the player is moving to another seat in the vehicle or exiting.
-- Basically this function cleans up all the parameters on the vehicle and the
-- player so that they reflect the proper state.
--
-- Note that this function does not do anything unless the player is exiting
-- the vehicle (i.e. exiting = true).
--
-- \param[in] vehicle
-- The vehicle entity that the player is in.
--
-- \param[in] seat
-- The seat entity that the player is leaving.
--
-- \param[in] entityId
-- The Id of the player leaving the seat.
--
-- \param[in] exiting
-- True if the player is exiting the vehicle. False if the player is only
-- switching to a different seat in the same vehicle.
-- =============================================================================
function PowerStruggle:OnLeaveVehicleSeat(vehicle, seat, entityId, exiting)
    if (self.isServer) then
        if (exiting) then
            --
            -- Determine if the vehicle is empty now that the player exited. This is
            -- done by scanning through all the seats in the vehicle and checks to
            -- see if there is a passengerId associated with each seat that does not
            -- match the player Id of the player exiting.
            --
            local empty=true;
            for i,seat in pairs(vehicle.Seats) do
                local passengerId = seat:GetPassengerId();
                if (passengerId and passengerId~=NULL_ENTITY and passengerId~=entityId) then
                    empty=false;
                    break;
                end
            end

            local player=System.GetEntity(entityId);
            --
            -- If the vehicle is now empty then set the last owner id for the vehicle
            -- to the player id and then set the last vehicle id for the player to the
            -- vehicle id.
            --
            if (empty) then
                vehicle.lastOwnerId=entityId;
                if (player) then
                    player.lastPSVehicleId=vehicle.id;
                end
            end
            --
            -- The vehicles owner id is set to null entity so that the players team
            -- mates can enter the vehicle. If this didn't happen then only the player
            -- could get back in the vehicle.
            --
            if(entityId==vehicle.vehicle:GetOwnerId()) then
                vehicle.vehicle:SetOwnerId(NULL_ENTITY);
            end

            --
            -- The last entered vehicle parameter is used for no collision damage within
            -- x seconds of the player exiting the vehicle. This must be set whenever a
            -- player exits a vehicle.
            --
            if(player) then
                player.lastExitedVehicleId = vehicle.id;
                player.lastExitedVehicleTime = _time;
            end
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle:VehicleOwnerDeath
--
-- This function is called whenever a player is killed. It simply calls the
-- AbandonPlayerVehicle function to handle any vehicle abandon related actions
-- that need to be taken. This function doesn't do any logical checks itself.
--
-- \param[in] player
-- The object of the player that just died.
-- =============================================================================
function PowerStruggle:VehicleOwnerDeath(player)
    self:AbandonPlayerVehicle(player.id);
end


-- =============================================================================
-- Function: PowerStruggle.Server:OnPlayerKilled
--
-- This function is called when a player is killed and it handles cleaning up
-- the players vehicle as well as adding the player to the revive cue and
-- restoring the players PP if needed.
--
-- \param[in] hit
-- Structure containing all the data associated with the hit that killed the
-- player.
-- =============================================================================
function PowerStruggle.Server:OnPlayerKilled(hit)
    TeamInstantAction.Server.OnPlayerKilled(self, hit);

    local deadGuy=hit.target;

    if (deadGuy and deadGuy.actor) then
        self:VehicleOwnerDeath(deadGuy);
        local revive=self.reviveQueue[deadGuy.id];

        --
        -- If the player killed a team mate then exit the function here without
        -- restoring any PP.
        --
        if (revive and revive.tk==true) then
            return
        end

        --
        -- Restore the players PP based on the players rank. This only needs to be
        -- done if the players current PP is less than the minimum PP specified for
        -- the players rank.
        --
        local rank=self.rankList[self:GetPlayerRank(deadGuy.id)];
        if (rank and rank.min_pp and rank.min_pp>0) then
            local currentpp=self:GetPlayerPP(deadGuy.id);
            if (currentpp<rank.min_pp) then
                --
                -- The players current PP amount is less than the minimum PP for his
                -- rank so award him the difference so that he has the minimum amount
                -- on spawn.
                --
                self:AwardPPCount(deadGuy.id, rank.min_pp-currentpp);
            end
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle:ShatterEntity
--
-- This function is called when an entity is shattered.
--
-- \param[in] entityId
-- The Id of the entity that is shattered.
--
-- \param[in] hit
-- Structure containing the data associated with the hit.
-- =============================================================================
function PowerStruggle:ShatterEntity(entityId, hit)
    TeamInstantAction.ShatterEntity(self, entityId, hit);

    if (hit.target and hit.target.actor) then
        self:VehicleOwnerDeath(hit.target);
    end
end


-- =============================================================================
-- Function: PowerStruggle.Server:OnUpdate
--
-- This function is called periodically to update things.
--
-- \param[in] frameTime
-- The current frame time of the game.
-- =============================================================================
function PowerStruggle.Server:OnUpdate(frameTime)
    TeamInstantAction.Server.OnUpdate(self, frameTime);
    self:UpdateUnclaimedVehicles(frameTime);
end


-- =============================================================================
-- Function: PowerStruggle.Client:OnUpdate
--
-- This function is called periodically to update things.
--
-- \param[in] frameTime
-- The current frame time of the game.
-- =============================================================================
function PowerStruggle.Client:OnUpdate(frameTime)
    TeamInstantAction.Client.OnUpdate(self, frameTime);
    self:UpdateObjectives();
end


-- =============================================================================
-- Function: PowerStruggle.Server:OnTimer
--
-- This function is called whenever a timer reaches its target time.
--
-- \param[in] timerId
-- The id of the timer that had the event.
--
-- \param[in] msec
-- See function header comments for TeamInstantAction.Server.OnTimer().
-- =============================================================================
function PowerStruggle.Server:OnTimer(timerId, msec)
    --
    -- NUKE SPECTATE TIMER EVENT
    --
    -- This happens at the end of a power struggle match when a player successfully
    -- nukes the enemies HQ. All players except for the one that nuked the HQ have
    -- their inventory destroyed and are switched into spectator mode to view the
    -- player that did nuke the HQ.
    --
    if(timerId == self.NUKE_SPECTATE_TIMERID) then
        local players=self.game:GetPlayers();
        local targetplayer = System.GetEntity(self.nukePlayer or NULL_ENTITY);
        if (players) then
            for i,player in pairs(players) do
                if(targetplayer and player.id ~= self.nukePlayer) then
                    player.inventory:Destroy();
                    self.game:ChangeSpectatorMode(player.id, 3, targetplayer.id);
                else
                    -- oops. Spectate the HQ directly?
                end
            end
        end
    end

    --
    -- Call the OnTimer function in TIA class to check for other types of timers
    -- that may have had events take place.
    --
    TeamInstantAction.Server.OnTimer(self, timerId, msec);
end


-- =============================================================================
-- Function: PowerStruggle.Client:OnRevive
--
-- This function is called to revive the player after a death.
--
-- \param[in] playerId
-- \param[in] pos
-- \param[in] rot
-- \param[in] teamId
-- See function header comments for TeamInstantAction.Client.OnRevive().
-- =============================================================================
function PowerStruggle.Client:OnRevive(playerId, pos, rot, teamId)
    TeamInstantAction.Client.OnRevive(self, playerId, pos, rot, teamId);
end

-- =============================================================================
-- Function: PowerStruggle.Client:OnReviveInVehicle
--
-- This function is called when the player is being revived in a vehicle
-- that has an attached spawn group.
--
-- \param[in] playerId
-- \param[in] vehicleId
-- \param[in] seatId
-- \param[in] teamId
-- See function header comments for TeamInstantAction.Client.OnRevive().
-- =============================================================================
function PowerStruggle.Client:OnReviveInVehicle(playerId, vehicleId, seatId, teamId)
    TeamInstantAction.Client.OnRevive(self, playerId, vehicleId, seatId, teamId);
end


-- =============================================================================
-- Function: PowerStruggle:OnTick
--
-- This function is called periodically to handle periodic tasks.
-- =============================================================================
function PowerStruggle:OnTick()

    if (self:GetState()~="PostGame") then
        self:UpdateReviveQueue();
    end
    TeamInstantAction.OnTick(self);
end


-- =============================================================================
-- Function: PowerStruggle.Server:RequestRevive
--
-- This function determines whether to add a dead player to the revive queue
-- and add's the player to it.
--
-- \param[in] playerId
-- The id of the player requesting to be revived.
-- =============================================================================
function PowerStruggle.Server:RequestRevive(playerId)
    local player = System.GetEntity(playerId);

    if (player and player.actor) then
        --
        -- Allow respawn if the player is not already in the revive queue and
        -- one of the follow conditions are true...
        -- 1. The player is a spectator and is assigned to a team.
        -- 2. The player is dead and died more than 2.5 seconds ago.
        --
        if (((player.actor:GetSpectatorMode() == 3 and self.game:GetTeam(playerId)~=0) or (player:IsDead() and player.death_time and _time-player.death_time>2.5)) and (not self:IsInReviveQueue(playerId))) then
            self:QueueRevive(playerId);
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle:QueueRevive
--
-- This function queue's the player for revival.
--
-- \param[in] playerId
-- The id of the player to be revived.
-- =============================================================================
function PowerStruggle:QueueRevive(playerId)
    local revive=self.reviveQueue[playerId];

    if (not revive) then
        self:ResetRevive(playerId);
        revive=self.reviveQueue[playerId];
    end

    revive.active=true;

    local player=System.GetEntity(playerId);
    if (player) then
        self.channelSpectatorMode[player.actor:GetChannel()]=nil;
    end
end


-- =============================================================================
-- Function: PowerStruggle:ClearReviveQueue
--
-- This function clears the revive queue.
-- =============================================================================
function PowerStruggle:ClearReviveQueue()
    for playerId,revive in pairs(self.reviveQueue) do
        revive.active=false;
        revive.announced=nil;
        revive.overdue=nil;
        revive.overdue_clearance=nil;
        revive.items={};
        revive.items_price=0;
        revive.ammo={};
        revive.ammo_price=0;
    end
end


-- =============================================================================
-- Function: PowerStruggle:FlushReviveQueue
--
-- This function flushes the revive queue.
-- =============================================================================
function PowerStruggle:FlushReviveQueue()
    for playerId,revive in pairs(self.reviveQueue) do
        if (revive.active) then
            local player=System.GetEntity(playerId);
            if (player) then
                if (self:RevivePlayer(player.actor:GetChannel(), player)) then
                    revive.active=false;
                    revive.announced=nil;
                    revive.overdue=nil;
                    revive.overdue_clearance=nil;
                end
            end
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle:ReviveAllPlayers
--
-- This function revives all the players in the game.
-- =============================================================================
function PowerStruggle:ReviveAllPlayers()
    -- set the teams in the revive queue
    for playerId,revive in pairs(self.reviveQueue) do
        if (revive.active) then
            revive.active=false;
            revive.announced=nil;
            revive.overdue=nil;
            revive.overdue_clearance=nil;

            revive.items={};
            revive.items_price=0;
            revive.ammo={};
            revive.ammo_price=0;

            local player=System.GetEntity(playerId);
            if (player and player.actor:GetSpectatorMode()~=0) then
                self.game:ChangeSpectatorMode(player.id, 0, NULL_ENTITY);
            end
        end
    end

    TeamInstantAction.ReviveAllPlayers(self);
end


-- =============================================================================
-- Function: PowerStruggle:ResetRevive
--
-- This function resets the revive parameters for a specific player.
--
-- \param[in] playerId
-- The id of the player in question.
--
-- \param[in] remove
-- If set to true then remove the player from the revive queue.
-- =============================================================================
function PowerStruggle:ResetRevive(playerId, remove)
    if (remove) then
        self.reviveQueue[playerId]=nil;
    else
        local revive=self.reviveQueue[playerId];
        if (not revive) then
            revive={};
            self.reviveQueue[playerId]=revive;
        end

        revive.active=false;
        revive.announced=nil;
        revive.overdue=nil;
        revive.overdue_clearance=nil;
        revive.tk=nil;

        revive.items={};
        revive.items_price=0;
        revive.ammo={};
        revive.ammo_price=0;
    end

    local player = System.GetEntity(playerId);
    if(player and player.actor) then
        self.onClient:ClReviveCycle(player.actor:GetChannel(), false);
    end
end


-- =============================================================================
-- Function: PowerStruggle:IsInReviveQueue
--
-- This function checks to see if the player is in the revive queue.
--
-- \param[in] playerId
-- The id of the player in question.
--
-- \return
-- True if the player is in the revive queue.
-- =============================================================================
function PowerStruggle:IsInReviveQueue(playerId)
    return self.reviveQueue[playerId] and self.reviveQueue[playerId].active;
end


-- =============================================================================
-- Function: PowerStruggle:CommitRevivePurchases
--
-- This function handles any item or ammo purchases made by the player
-- while they were waiting to be respawned.
--
-- \param[in] playerId
-- The id of the player in question.
-- =============================================================================
function PowerStruggle:CommitRevivePurchases(playerId)
    local revive=self.reviveQueue[playerId];
    local player=System.GetEntity(playerId);

    --
    -- Handle all of the ammo purchases.
    --
    for ammo,c in pairs(revive.ammo) do
        player.actor:SetInventoryAmmo(ammo, c, CLIENT_SIDE + SERVER_SIDE);
    end
    self:AwardPPCount(playerId, -revive.ammo_price);

    --
    -- Handle all of the item purchases.
    --
    local ok=false;
    for i,itemName in ipairs(revive.items) do
        ok=self:BuyItem(playerId, itemName, true, true);

        if (not ok) then
            break;
        end
    end

    --
    -- Clear the revive data for the items and ammo purchased.
    --
    revive.ammo={};
    revive.items={};
    revive.items_price=0;
    revive.ammo_price=0;
end


-- =============================================================================
-- Function: PowerStruggle:UpdateReviveQueue
--
-- This function
--
-- \param[in]
--
-- =============================================================================
function PowerStruggle:UpdateReviveQueue()
    local reviveTimer=self.game:GetRemainingReviveCycleTime();

    if (reviveTimer>0) then
        for playerId,revive in pairs(self.reviveQueue) do
            if (revive.active) then
                local player=System.GetEntity(playerId);
                if (player and player.spawnGroupId and player.spawnGroupId~=NULL_ENTITY) then
                    if ((not revive.announced) and (not revive.overdue)) then
                        self.onClient:ClReviveCycle(player.actor:GetChannel(), true);
                        revive.announced=true;
                    end
                elseif (revive.announced) then
                    --
                    -- Spawngroup got invalidated while spawn cycle was up,
                    -- so need to make sure it gets sent again after the situation
                    -- is cleared.
                    --
                    revive.announced=nil;
                end

                if (revive.overdue and self:CanRevive(playerId)) then
                    if (not revive.overdue_clearance) then
                        revive.overdue_clearance=_time;
                    elseif (_time-revive.overdue_clearance>=2) then
                        self:RevivePlayerInQueue(player, revive);
                    end
                else
                    revive.overdue_clearance=nil;
                end
            end
        end

        --
        -- If player has been dead more than 5s and isn't spectating,
        -- auto-switch to spectator mode 3.
        --
        local players=self.game:GetPlayers();
        if (players) then
            for i,player in pairs(players) do
                if(player and player:IsDead() and player.death_time and _time-player.death_time>5 and player.actor:GetSpectatorMode() == 0) then
                    self.Server.RequestSpectatorTarget(self, player.id, 1);
                end
            end
        end
    end

    if (reviveTimer<=0) then
        self.game:ResetReviveCycleTime();

        for i,teamId in ipairs(self.teamId) do
            self:UpdateTeamRanks(teamId);
        end

        for playerId,revive in pairs(self.reviveQueue) do
            local player=System.GetEntity(playerId);
            if (player) then
                if (revive.active and self:CanRevive(playerId)) then
                    self:RevivePlayerInQueue(player, revive);
                elseif (revive.active) then
                    local groupId=player.spawnGroupId;
                    if ((not groupId) or (groupId==NULL_ENTITY)) then
                        if (not revive.overdue) then
                            if (not revive.announced) then
                                local channelId=player.actor:GetChannel();
                                self.onClient:ClReviveCycle(channelId, true);
                                self.onClient:ClSpawnGroupInvalid(channelId, NULL_ENTITY);
                                revive.announced=true;
                            end
                            revive.overdue=true;
                        end
                    end
                end
            end
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle:RevivePlayerInQueue
--
-- This function revives a player in the queue.
--
-- \param[in] player
-- The object of the player being revived.
--
-- \param[in] revive
-- Structure holding all of the data associated with the revive cycle.
-- =============================================================================
function PowerStruggle:RevivePlayerInQueue(player, revive)
    revive.active=false;

    self:RevivePlayer(player.actor:GetChannel(), player);
    self:CommitRevivePurchases(player.id);

    revive.tk=nil;
    revive.announced=nil;
    revive.overdue=nil;
    revive.overdue_clearance=nil;
end


-- =============================================================================
-- Function: PowerStruggle:RevivePlayer
--
-- This function revives the player.
--
-- \param[in] channelId
-- The id of the channel for this player.
--
-- \param[in] player
-- The object for the player.
--
-- \return
-- True if the player is revived successfully.
-- =============================================================================
function PowerStruggle:RevivePlayer(channelId, player)
    if (player.actor:GetSpectatorMode()~=0) then
        self.game:ChangeSpectatorMode(player.id, 0, NULL_ENTITY);
    end

    local result=TeamInstantAction.RevivePlayer(self, channelId, player);

    self:ResetUnclaimedVehicle(player.id, false);

    player.lastPSVehicleId=nil;

    return result;
end


-- =============================================================================
-- Function: PowerStruggle.Server:OnChangeSpectatorMode
--
-- This function is the event handler for when a player changes their
-- spectator mode.
--
-- \param[in] playerId
-- The id of the player.
--
-- \param[in] mode
-- The new spectating mode.
--
-- \param[in] targetId
-- \param[in] resetAll
-- See function header comments for TeamInstantAction.Server.OnChangeSpectatorMode().
-- =============================================================================
function PowerStruggle.Server:OnChangeSpectatorMode(playerId, mode, targetId, resetAll)
    if(resetAll) then
        --self:ResetPP(playerId);        --Don't reset scores just for going into spectator mode ffs
        --self:ResetCP(playerId);
    end

    TeamInstantAction.Server.OnChangeSpectatorMode(self, playerId, mode, targetId, resetAll, true);

    if(resetAll and mode>0) then
        self:ResetRevive(playerId);
    end
end


-- =============================================================================
-- Function: PowerStruggle.Server:OnChangeTeam
--
-- This function is the event handler called when a player changes teams. Note
-- that only players get this. For entities, OnSetTeam is used.
--
-- \param[in] playerId
-- The id of the player switching teams.
--
-- \param[in] teamId
-- The id of the team that the player is joining.
-- =============================================================================
function PowerStruggle.Server:OnChangeTeam(playerId, teamId)
    local oldTeamId=self.game:GetTeam(playerId);
    if (teamId ~= oldTeamId) then
        local player=System.GetEntity(playerId);
        if (player) then
            --
            -- Do not allow a player in the penalty box to change teams. If the
            -- player tries then display a warning message.
            --
			local profile = XGetIdentityValue(player, "profile");
			if (AEGIS.PlayerInformationByProfile[profile] and AEGIS.PlayerInformationByProfile[profile].PenaltyBox) then
                self.game:SendTextMessage(TextMessageError, "Team change denied while in PENALTY BOX.", TextMessageToClient, playerId);
                return;
            end

            if (player.last_team_change and teamId~=0) then
                if (self:GetState()=="InGame") then
                    --
                    -- Do not allow a player to change teams too often. The time
                    -- between team changes is defined as TEAM_CHANGE_MIN_TIME
                    -- near the top of this file. If it has not been long enough
                    -- from the last team change then display a message to the
                    -- player stating such and exit this function.
                    -- Allow protected admins to bypass
                    --
                    if (_time-player.last_team_change<self.TEAM_CHANGE_MIN_TIME) then
                        if ((not player.last_team_change_warning) or (_time-player.last_team_change_warning>=4)) then
                            player.last_team_change_warning=_time;
                            self.game:SendTextMessage(TextMessageError, "@mp_TeamChangeLimit", TextMessageToClient, playerId, self.TEAM_CHANGE_MIN_TIME-math.floor(_time-player.last_team_change+0.5));
                        end
                        return;
                    end
                end
            end

            --
            -- Do not allow a player to switch teams if it could result in an
            -- unbalanced team condition. If this is the case then display a
            -- message to the player and then exit this function.
            -- Allow protected admins to bypass
            --
            if (self:IsTeamLocked(teamId, playerId)) then
                if ((not player.last_team_locked_warning) or (_time-player.last_team_locked_warning>=4)) then
                    player.last_team_locked_warning=_time;
                    Log("Team change request by %s denied: team %d has too many players", EntityName(playerId), teamId);
                    self.game:SendTextMessage(TextMessageError, "@mp_TeamLockedTooMany", TextMessageToClient, playerId);
                end
                return;
            end

            --
            -- If we have gotten to this point then switching teams is OK. If the
            -- player is currently alive and not spectating then kill the player
            -- so the team change can take effect.
            --
            if (player.actor:GetHealth()>0 and player.actor:GetSpectatorMode()==0) then
                self:KillPlayer(player);
            end

            --
            -- Now make the team change by setting the player to the correct
            -- team and requesting a valid spawn group for the player.
            --
            if (teamId~=0) then
                self:QueueRevive(playerId);
                self.game:SetTeam(teamId, playerId);
                self.Server.RequestSpawnGroup(self, player.id, self.game:GetTeamDefaultSpawnGroup(teamId) or NULL_ENTITY, true);

                player.last_team_change=_time;
            end
        end

        --
        -- If the player has any jobs in work at any factory then cancel it
        -- when the team switch is made.
        --
        for i,factory in pairs(self.factories) do
            factory:CancelJobForPlayer(playerId);
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle.Server:OnSetTeam
--
-- This function is used to change the team of entities.
--
-- \param[in] entityId
-- The id of the entity to be swtiched to a different team.
--
-- \param[in] teamId
-- The id of the new team that the entity should be set to.
-- =============================================================================
function PowerStruggle.Server:OnSetTeam(entityId, teamId)
    local entity=System.GetEntity(entityId);
    if (entity) then
        entity.last_scanned=nil;
    end
end


-- =============================================================================
-- Function: PowerStruggle.Client:OnSetTeam
--
-- This function is used to change the team of entities.
--
-- \param[in] entityId
-- The id of the entity to be swtiched to a different team.
--
-- \param[in] teamId
-- The id of the new team that the entity should be set to.
-- =============================================================================
function PowerStruggle.Client:OnSetTeam(entityId, teamId)
    if (entityId == g_localActorId) then
        if (HUD) then
            HUD.UpdateBuyList();
            HUD.SetObjectiveStatus("PS.Obj1_CapturePT", MO_DEACTIVATED, 1);
            HUD.SetObjectiveStatus("PS.Obj2_SecureAliens", MO_DEACTIVATED, 1);
            HUD.SetObjectiveStatus("PS.Obj3_BuildTAC", MO_DEACTIVATED, 1);
            HUD.SetObjectiveStatus("PS.Obj4_DestroyHQ", MO_DEACTIVATED, 1);
            HUD.SetObjectiveStatus("PS.SecObj1_Factory", MO_DEACTIVATED, 1);
            HUD.SetObjectiveStatus("PS.SecObj2_Bunker", MO_DEACTIVATED, 1);
            HUD.SetObjectiveStatus("PS.SecObj3_Turret", MO_DEACTIVATED, 1);
            HUD.SetMainObjective("PS.Obj1_CapturePT");

            self:UpdateObjectives();
        end
    else
        local entity=System.GetEntity(entityId);
        if (entity.GetBuyFlags and (entity:GetBuyFlags()~=0)) then -- TODO: more robust way to check if the entity is a buy area
            HUD.UpdateBuyList();
        end
    end

    local entity=System.GetEntity(entityId);
    if (entity) then
        if (entity.OnSetTeam) then
            entity:OnSetTeam(teamId);
        end
    end
end

----------------------------------------------------------------------------------------------------
-- =============================================================================
-- Function: PowerStruggle:ShouldAutoRespawn()
--
-- Never Auto-Respawn in PS mode
-- =============================================================================

function PowerStruggle:ShouldAutoRespawn()
	return false;
end


-- =============================================================================
-- Function: PowerStruggle:ProcessScores
--
-- This function is called when a player makes a kill.
--
-- \param[in] hit
-- Structure containing data relating to the hit.
--
-- \param[in] tk
-- See function header comments for TeamInstantAction.ProcessScores().
-- =============================================================================
function PowerStruggle:ProcessScores(hit, tk)
    TeamInstantAction.ProcessScores(self, hit, tk);

    if (self:GetState()=="PostGame") then
        return;
    end

    --
    -- If the shooter is the player then award them PP and CP for the kill.
    --
    local shooter=hit.shooter;
    if (shooter and shooter.actor and shooter.actor:IsPlayer()) then
        self:AwardKillPP(hit);
        self:AwardKillCP(hit);
    end
end

----------------------------------------------------------------------------------------------------
-- =============================================================================
-- PowerStruggle:SetTeamScore(teamId, score)
--
-- \param[in] teamId
-- Defines team for which score should be changed (1 = NK, 2 = US)
--
-- \param[in] score
-- Score to set team score to
--
-- Sets a team's score to given
-- =============================================================================
function PowerStruggle:SetTeamScore(teamId, score)
	self.game:SetSynchedGlobalValue(self.TEAMSCORE_TEAM0_KEY+teamId, score);
end


-- =============================================================================
-- Function: PowerStruggle:ProcessVehicleScores
--
-- This function is called when a player kills a vehicle.
--
-- \param[in] targetId
-- The id of the vehicle that was killed.
--
-- \param[in] shooterId
-- The id of the player that made the kill.
-- =============================================================================
function PowerStruggle:ProcessVehicleScores(targetId, shooterId)

    local target=System.GetEntity(targetId);

    if (shooterId) then
        local vTeam=self.game:GetTeam(targetId);
        local sTeam=self.game:GetTeam(shooterId);

        --
        -- Make sure the vehicle and the player are not on the same team. If they
        -- are then we do not reward the player for the kill.
        --
        if ((vTeam~=0) and (vTeam~=sTeam)) then
            --
            -- Get the minimum base PP and CP for killing a vehicle.
            --
            local pp=self.ppList.VEHICLE_KILL_MIN;
            local cp=self.cpList.VEHICLE_KILL_MIN;

            if (target.builtas) then
                local def=self:GetItemDef(target.builtas);
                if (def) then
                    --
                    -- Calculate the actual PP and CP to be awarded based on
                    -- vehicle type and multipliers.
                    --
                    pp=math.max(pp, math.floor(def.price*self.ppList.VEHICLE_KILL_MULT));
                    cp=math.max(cp, math.floor(def.price*self.cpList.VEHICLE_KILL_MULT));
                end
            end

            --
            -- Award PP and CP to the player.
            --
            self:AwardPPCount(shooterId, pp);
            self:AwardCPCount(shooterId, cp);
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle:PrecacheLevel
-- =============================================================================
function PowerStruggle:PrecacheLevel()
    TeamInstantAction.PrecacheLevel(self);

    for i,v in pairs(self.buyList) do
        if (v.weapon or v.equip or v.proto) then
            if (v.class) then
                if (type(v.class)=="string" and v.class~="") then
                    CryAction.CacheItemGeometry(v.class);
                    CryAction.CacheItemSound(v.class);
                elseif (type(v.class)=="table") then
                    for k,j in pairs(v.class) do
                        CryAction.CacheItemGeometry(j);
                        CryAction.CacheItemSound(j);
                    end
                end
            end
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle.Client:ClResetBuyZones
--
-- This function resets buy zone parameters in the players HUD.
-- =============================================================================
function PowerStruggle.Client:ClResetBuyZones()
    if (g_localActor and HUD) then
        HUD.ResetBuyZones();
        HUD.UpdateBuyList();
    end
end


-- =============================================================================
-- Function: PowerStruggle.Client:ClEnterBuyZone
--
-- This function updates buy zone parameters in the players HUD when the
-- player enters a buy zone.
--
-- \param[in] zoneId
-- The id of the buy zone that was entered.
--
-- \param[in] enable
-- =============================================================================
function PowerStruggle.Client:ClEnterBuyZone(zoneId, enable)
    if (g_localActor and HUD) then
        HUD.EnteredBuyZone(zoneId, enable);
        HUD.UpdateBuyList();
    end
end


-- =============================================================================
-- Function: PowerStruggle.Client:ClEnterServiceZone
--
-- This function updates service zone parameters in the players HUD when the
-- player enters a buy zone.
--
-- \param[in] zoneId
-- The id of the buy zone that was entered.
--
-- \param[in] enable
-- =============================================================================
function PowerStruggle.Client:ClEnterServiceZone(zoneId, enable)
    if (g_localActor and HUD) then
        HUD.EnteredServiceZone(zoneId, enable); -- change this to be service zone if needed...
        HUD.UpdateBuyList();
    end
end


-- =============================================================================
-- Function: PowerStruggle.Client:ClReviveCycle
--
-- This function tells the client to display the revive cycle (countdown).
--
-- \param[in] show
-- If true then the client will show the countdown timer.
-- =============================================================================
function PowerStruggle.Client:ClReviveCycle(show)
    HUD.ShowReviveCycle(show);
end


-- =============================================================================
-- Function: PowerStruggle.Client:ClSpawnGroupInvalid
--
-- This function tells the client a spawn group went invalid.
--
-- \param[in] spawnGroupId
-- The id of the spawn group that went invalid.
-- =============================================================================
function PowerStruggle.Client:ClSpawnGroupInvalid(spawnGroupId)
    TeamInstantAction.Client.ClSpawnGroupInvalid(self, spawnGroupId);
end


-- =============================================================================
-- Function: PowerStruggle:ResetPlayers
--
-- This function resets the players status including PP and CP.
-- =============================================================================
function PowerStruggle:ResetPlayers()
    TeamInstantAction.ResetPlayers(self);

    self:ClearReviveQueue();

    local players=self.game:GetPlayers();
    if (players) then
        for i,player in pairs(players) do
            player.last_team_change=nil;
            if (player.actor:GetSpectatorMode()==0 or player.actor:GetSpectatorMode()==3) then
                self:ResetPP(player.id);
                self:ResetCP(player.id);
                self:SetPlayerPP(player.id, self.ppList.START);

                local teamId=self.game:GetTeam(player.id) or 0;
                self.Server.RequestSpawnGroup(self, player.id, self.game:GetTeamDefaultSpawnGroup(teamId) or NULL_ENTITY, true);
            end
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle:ResetMinimap
--
-- This function resets the minimap.
-- =============================================================================
function PowerStruggle:ResetMinimap()
    self.game:ResetMinimap();
end


-- =============================================================================
-- Function: PowerStruggle:OnDisarmed
--
-- This function awards PP to the player whenever the player disarms
-- another entity. The amount of PP given is set in the ppList.DISARM
-- variable set near the beginning of this file.
--
-- \param[in] entityId
-- The id of the entity that was disarmed.
--
-- \param[in] disarmerId
-- The id of the player that did the disarming.
-- =============================================================================
function PowerStruggle:OnDisarmed(entityId, disarmerId)
    if (self.game:GetTeam(entityId)~=self.game:GetTeam(disarmerId)) then
        self:AwardPPCount(disarmerId, self.ppList.DISARM);
    end
end


-- =============================================================================
-- Function: PowerStruggle.Server:OnAddTaggedEntity
--
-- This function gives players PP and CP for tagging enemies. The most
-- common way to tag an enemy is by using the radar scanner. Note that there
-- is a minimum time between scans where the player will not be rewarded for
-- tagging an enemy. This is currently hard coded to 16 in the code below.
--
-- \param[in] shooterId
-- The id of the player that did the tagging.
--
-- \param[in] targetId
-- The id of the player that got tagged.
-- =============================================================================
function PowerStruggle.Server:OnAddTaggedEntity(shooterId, targetId)
    local shooterTeam = self.game:GetTeam(shooterId);
    local targetTeam = self.game:GetTeam(targetId);
    if((targetTeam~=0) and (shooterTeam ~= targetTeam)) then
        local target=System.GetEntity(targetId);
        if (target) then
            if ((not target.last_scanned) or (_time-target.last_scanned>16)) then
                self:AwardPPCount(shooterId, self.ppList.TAG_ENEMY);
                self:AwardCPCount(shooterId, self.cpList.TAG_ENEMY);
                target.last_scanned=_time;
            end
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle:DefaultState
-- =============================================================================
function PowerStruggle:DefaultState(cs, state)
    local default=self[cs];
    self[cs][state]={
        OnClientConnect = default.OnClientConnect,
        OnClientDisconnect = default.OnClientDisconnect,
        OnClientEnteredGame = default.OnClientEnteredGame,
        OnDisconnect = default.OnDisconnect, -- client only
        OnActorAction = default.OnActorAction, -- client only
        OnStartLevel = default.OnStartLevel,
        OnStartGame = default.OnStartGame,

        OnKill = default.OnKill,
        OnHit = default.OnHit,
        OnFreeze = default.OnFreeze,
        OnExplosion = default.OnExplosion,
        OnChangeTeam = default.OnChangeTeam,
        OnChangeSpectatorMode = default.OnChangeSpectatorMode,
        RequestSpectatorTarget = default.RequestSpectatorTarget,
        OnSetTeam = default.OnSetTeam,
        OnItemPickedUp = default.OnItemPickedUp,
        OnItemDropped = default.OnItemDropped,
        OnMineDisarmed = default.OnMineDisarmed,
        OnAddTaggedEntity = default.OnAddTaggedEntity,

        OnTimer = default.OnTimer,
        OnUpdate = default.OnUpdate,
    }
end


PowerStruggle:DefaultState("Server", "Reset");
PowerStruggle:DefaultState("Client", "Reset");


PowerStruggle:DefaultState("Server", "PreGame");
PowerStruggle:DefaultState("Client", "PreGame");


PowerStruggle:DefaultState("Server", "InGame");
PowerStruggle:DefaultState("Client", "InGame");


PowerStruggle:DefaultState("Server", "PostGame");
PowerStruggle:DefaultState("Client", "PostGame");


PowerStruggle.Server.PostGame.OnChangeTeam = nil;
--PowerStruggle.Server.PostGame.OnChangeSpectatorMode = nil;


-- =============================================================================
-- Function: PowerStruggle.Client.PreGame:OnBeginState
--
-- This function sets the state of the player when PreGame state is set.
-- =============================================================================
function PowerStruggle.Client.PreGame:OnBeginState()
    TeamInstantAction.Client.PreGame.OnBeginState(self);
end


-- =============================================================================
-- Function: PowerStruggle.Server.PreGame:OnBeginState
--
-- This function sets the state of the player when PreGame state is set.
-- =============================================================================
function PowerStruggle.Server.PreGame:OnBeginState()
    self:ResetAlerts();
    self:ResetMinimap();

    TeamInstantAction.Server.PreGame.OnBeginState(self);
end


-- =============================================================================
-- Function: PowerStruggle.Server.PreGame:OnTick
--
-- This function is called periodically to handle periodic tasks when the
-- game state is set to PreGame.
-- =============================================================================
function PowerStruggle.Server.PreGame:OnTick()
    TeamInstantAction.Server.PreGame.OnTick(self);
end


-- =============================================================================
-- Function: PowerStruggle.Client.PreGame:OnTick
--
-- This function is called periodically to handle periodic tasks when the
-- game state is set to PreGame.
-- =============================================================================
function PowerStruggle.Client.PreGame:OnTick()
    TeamInstantAction.Client.PreGame.OnTick(self);
end


-- =============================================================================
-- Function: PowerStruggle.Server.InGame:OnBeginState
--
-- This function sets the state of the player when InGame state is set.
-- =============================================================================
function PowerStruggle.Server.InGame:OnBeginState()
    TeamInstantAction.Server.InGame.OnBeginState(self);

    self:ResetAlerts();
    self:ResetMinimap();

    CryAction.SendGameplayEvent(NULL_ENTITY, eGE_GameStarted, "", 1);--server
end


-- =============================================================================
-- Function: PowerStruggle.Client.InGame:OnBeginState
--
-- This function sets the state of the player when InGame state is set.
-- =============================================================================
function PowerStruggle.Client.InGame:OnBeginState()
    CryAction.SendGameplayEvent(NULL_ENTITY, eGE_GameStarted, "", 0);--client
    self.overtimeAdded = nil;
end


-- =============================================================================
-- Function: PowerStruggle.Server.InGame:OnUpdate
-- =============================================================================
function PowerStruggle.Server.InGame:OnUpdate(frameTime)
    PowerStruggle.Server.OnUpdate(self, frameTime);

    self:CheckTimeLimit();
    self:UpdateClAlerts();
end


-- =============================================================================
-- Function: PowerStruggle.Server.InGame:OnTick
--
-- This function is called periodically to handle periodic tasks when the
-- game state is set to InGame.
-- =============================================================================
function PowerStruggle.Server.InGame:OnTick()
    self:PowerTick();

    TeamInstantAction.Server.InGame.OnTick(self);
end


-- =============================================================================
-- Function: PowerStruggle.Server.PostGame:OnBeginState
--
-- This function sets the state of the player when PostGame state is set.
-- =============================================================================
function PowerStruggle.Server.PostGame:OnBeginState()
    TeamInstantAction.Server.PostGame.OnBeginState(self);

    CryAction.SendGameplayEvent(NULL_ENTITY, eGE_GameEnd, "", 1);--server

    self:SetTimer(self.NUKE_SPECTATE_TIMERID, self.NUKE_SPECTATE_TIME);

    self:ResetAlerts();
end


-- =============================================================================
-- Function: PowerStruggle.Client.PostGame:OnBeginState
--
-- This function sets the state of the player when PostGame state is set.
-- =============================================================================
function PowerStruggle.Client.PostGame:OnBeginState()
    TeamInstantAction.Client.PostGame.OnBeginState(self);

    --This will be called in InstantAction.Client.PostGame.OnBeginState
    --CryAction.SendGameplayEvent(NULL_ENTITY, eGE_GameEnd, "", 1);--server

    if (HUD) then
        HUD.OpenPDA(false, false);
    end
end


-- =============================================================================
-- Function: PowerStruggle.Client.PostGame:OnEndState
--
-- This function sets the state of the player when PostGame state ends.
-- =============================================================================
function PowerStruggle.Client.PostGame:OnEndState()
    TeamInstantAction.Client.PostGame.OnEndState(self);

    if (self.victorySoundId) then
        Sound.StopSound(self.victorySoundId);
        self.victorySoundId=nil;
    end
end


-- =============================================================================
-- Function: PowerStruggle.Server:RequestSpectatorTarget
--
-- This function handles the clients request to spectate another player. The
-- player must either not be on a team or dead for this to work.
--
-- \param[in]
--
-- =============================================================================
function PowerStruggle.Server:RequestSpectatorTarget(playerId, change)
    local team = self.game:GetTeam(playerId);
    local player = System.GetEntity(playerId);
    local mode = player.actor:GetSpectatorMode();
    if(not player:IsDead() and team ~= 0 and mode ~= 3) then
        return;
    end

    TeamInstantAction.Server.RequestSpectatorTarget(self, playerId, change);
end


-- =============================================================================
-- Function: PowerStruggle.Server:SvRequestPP
--
-- This function only works when cheat mode is active.
-- =============================================================================
function PowerStruggle.Server:SvRequestPP(playerId, amount)
    if (g_gameRules.game:CanCheat()) then
        self:AwardPPCount(playerId, amount);
    end
end


-- =============================================================================
-- Function: PowerStruggle:OnTeamKill
--
-- This function is called whenever a player makes a team kill.
--
-- \param[in] targetId
-- The id of the player that was killed.
--
-- \param[in] shooterId
-- The id of the player that did the killing.
-- =============================================================================
function PowerStruggle:OnTeamKill(targetId, shooterId)
    local revive=self.reviveQueue[shooterId];
    if (not revive) then
        self:ResetRevive(shooterId);
        revive=self.reviveQueue[shooterId];
    end

    revive.tk=true;

    TeamInstantAction.OnTeamKill(self, targetId, shooterId);
end


-- =============================================================================
-- Function: GivePP
--
-- This function only works when cheat mode is on.
-- =============================================================================
function GivePP(amt)
    if (g_gameRules.isServer) then
        if (g_gameRules.game:CanCheat()) then
            g_gameRules:AwardPPCount(g_localActorId, amt);
        end
    else
        g_gameRules.server:SvRequestPP(g_localActorId, amt);
    end
end


-- =============================================================================
-- Function: GivePPToAll
--
-- This function only works when cheat mode is on.
-- =============================================================================
function GivePPToAll(amt)
    if (g_gameRules.game:CanCheat()) then
        for i,v in g_gameRules.game:GetPlayers() do
            g_gameRules:AwardPPCount(v.id, amt);
        end
    end
end


-- =============================================================================
-- Function: PowerStruggle:DisplayKillScores
--
-- This function does nothing.
-- =============================================================================
function PowerStruggle:DisplayKillScores()
    return false;
end


-- =============================================================================
-- Function: PowerStruggle:CalculateScore
--
-- \return
-- The number of kills that is passed into the function.
-- =============================================================================
function PowerStruggle:CalculateScore(deaths, kills, teamkills)
    return kills;
end


-- =============================================================================
-- Function: PowerStruggle:UpdateObjectives
--
-- This function updates the players objectives.
-- =============================================================================
function PowerStruggle:UpdateObjectives()
    if(g_localActorId) then
        local playerTId=self.game:GetTeam(g_localActorId);

        --
        -- Find out what team the PT factory is owned by.
        --
        local ptFactoryTId = 0;
        if(self.factories) then
            for i,factory in pairs(self.factories) do
                if (factory.Properties.buyOptions and factory.Properties.buyOptions.bPrototypes == 1) then
                    ptFactoryTId=self.game:GetTeam(factory.id);
                end
            end
        end

        --
        -- Find out how much power has been collected.
        --
        local power = self.game:GetSynchedGlobalValue(self.TEAMPOWER_TEAM0_KEY+playerTId);

        --work out if there is a TAC weapon somewhere
        --TODO

        --
        -- Update objectives: secondary always active.
        --
        HUD.SetObjectiveStatus("PS.SecObj1_Factory", MO_DEACTIVATED, 1);
        HUD.SetObjectiveStatus("PS.SecObj2_Bunker", MO_DEACTIVATED, 1);
        HUD.SetObjectiveStatus("PS.SecObj3_Turret", MO_DEACTIVATED, 1);

        if(ptFactoryTId==playerTId and playerTId~=0) then
            if(power==100) then
                local obj = HUD.GetMainObjective();
                if(obj ~= "PS.Obj4_DestroyHQ") then
                    HUD.SetMainObjective("PS.Obj3_BuildTAC");
                end
            else
                HUD.SetMainObjective("PS.Obj2_SecureAliens");
            end
        else
            HUD.SetMainObjective("PS.Obj1_CapturePT");
        end
    end
end


-- =============================================================================
-- Load the rest of the power struggle game mode scripts.
-- =============================================================================
Script.LoadScript("scripts/gamerules/powerstrugglebuying.lua", 1, 1);
Script.LoadScript("scripts/gamerules/powerstrugglerank.lua", 1, 1);
Script.LoadScript("scripts/gamerules/powerstrugglealert.lua", 1, 1);